#include "servicegraphics.h"

#include "graphics/renderwindow.h"
#include "graphics/vertexformat.h"
#include "graphics/graphicsmanager.h"
#include "graphics/texturemanager.h"
#include "graphics/light.h"
#include "graphics.opengl/renderer.h"
#include "application.h"

#include "graphicspipeline.h"

#include "cellclustermanager.h"

#include "graphicsmodel.h"
#include "graphicsmesh.h"
#include "graphicssimplemesh.h"

#include "inputs/manager.h"

using dw::graphics::Renderer;

namespace infinity {

/*static*/ const ServiceKey ServiceGraphics::Key = ServiceKey("Service::Graphics");
/*static*/ const std::string ServiceGraphics::CollectiblesBlocksPath = std::string("entities/collectibles/blocks/");
/*static*/ const std::string ServiceGraphics::CollectiblesItemsPath = std::string("entities/collectibles/items/");

/*virtual*/ ServiceGraphics::~ServiceGraphics()
{
}

/*virtual*/ Service::State ServiceGraphics::onInitializeMedia()
{
	State superState = super::onInitializeMedia();

	if (superState != State_Initialized)
	{
		return superState;
	}
	
	initializeGroups();
	initializePipelines();
	initializeMeshes();

	dw::inputs::InputManager& inputManager = dw::inputs::InputManager::getInstance();
	m_toggleWireframe.addCondition(dw::inputs::KeyStateCondition(OIS::KC_F1, dw::inputs::ButtonStateCondition::Pressed));
	inputManager.registerCommand(&m_toggleWireframe);

	return State_Initialized;
}

/*virtual*/ void ServiceGraphics::onUpdateMedia(dw::float32 deltaT)
{
	if (m_toggleWireframe.isActive())
	{
		m_pipelines[PIPELINE_TERRAIN_WIREFRAME]->setVisible(!m_pipelines[PIPELINE_TERRAIN_WIREFRAME]->isVisible());
		m_pipelines[PIPELINE_ENTITIES_WIREFRAME]->setVisible(!m_pipelines[PIPELINE_ENTITIES_WIREFRAME]->isVisible());
	}

	dw::graphics::Renderer& renderer = dw::graphics::GraphicsManager::getInstance().getRenderer();

	renderer.pushState();

	for (dw::uint32 pipelineIt = 0; pipelineIt < PIPELINES_COUNT; ++pipelineIt)
	{
		if (m_pipelines[pipelineIt] != NULL && m_pipelines[pipelineIt]->isVisible())
		{
			m_pipelines[pipelineIt]->render();
		}
	}
	
	renderer.popState();
}

/*virtual*/ Service::State ServiceGraphics::onTerminateMedia()
{
	State selfState = State_Terminating;

	dw::inputs::InputManager& inputManager = dw::inputs::InputManager::getInstance();
	inputManager.unregisterCommand(&m_toggleWireframe);

	if (m_renderWindow != NULL)
	{
		/*
		dw::graphics::GraphicsManager::getInstance().getRenderer().setRenderTarget(NULL);

		delete m_renderWindow;
		m_renderWindow = NULL;
		*/
		selfState = State_Terminated;
	}

	if (selfState == State_Terminated)
	{
		return super::onTerminateMedia();
	}

	return selfState;
}

graphics::Model* ServiceGraphics::createModel(Groups group)
{
	graphics::Model* newModel = new graphics::Model(group);

	graphics::SimpleMesh* newMesh = new graphics::SimpleMesh(
		dw::graphics::BufferObject::update_never, dw::graphics::IndexBuffer::uint16,
		dw::graphics::primitive::triangles, m_groups[group].vertexFormat);
	newMesh->m_isManaged = false;

	newModel->setMesh(newMesh);

	dw::uint32 pipelineBits = m_groups[group].pipelinesBits;
	dw::uint32 pipelineIt = 0;
	while (pipelineBits != 0)
	{
		if ((pipelineBits & 0x1) && m_pipelines[pipelineIt] != NULL)
		{
			m_pipelines[pipelineIt]->addModel(newModel);
		}
		pipelineIt++;
		pipelineBits >>= 1;
	}

	return newModel;
}

graphics::Model* ServiceGraphics::createModel(Groups group, const std::string& meshName)
{
	dwAssert(m_meshes.find(meshName) != m_meshes.end(), "Invalid parameter: meshName is not a registered mesh");

	graphics::Model* newModel = new graphics::Model(group);

	MeshesCollection::const_iterator foundIt = m_meshes.find(meshName);
	if (foundIt != m_meshes.end())
	{
		graphics::Mesh* mesh = m_meshes.find(meshName)->second;

		newModel->setMesh(mesh);

		dw::uint32 pipelineBits = m_groups[group].pipelinesBits;
		dw::uint32 pipelineIt = 0;
		while (pipelineBits != 0)
		{
			if (pipelineBits & 0x1)
			{
				m_pipelines[pipelineIt]->addModel(newModel);
			}
			pipelineIt++;
			pipelineBits >>= 1;
		}
	}

	return newModel;
}

void ServiceGraphics::releaseModel(graphics::Model* model)
{
	dwAssert(model != NULL, "Invalid parameter: model is null");

	dw::uint32 pipelineBits = m_groups[model->getGroup()].pipelinesBits;
	dw::uint32 pipelineIt = 0;
	while (pipelineBits != 0)
	{
		if (pipelineBits & 0x1)
		{
			m_pipelines[pipelineIt]->removeModel(model);
		}
		pipelineIt++;
		pipelineBits >>= 1;
	}

	if (model->getMesh() != NULL && !model->getMesh()->m_isManaged)
	{
		delete model->getMesh();
	}

	delete model;
}

void ServiceGraphics::initializeGroups()
{
	using dw::graphics::VertexFormat;
	using dw::graphics::VertexElement;
	
	static const VertexElement terrainElements[] = {
		{ VertexElement::vec3, VertexElement::position, 0 },
		{ VertexElement::vec3, VertexElement::normal, 0 },
		{ VertexElement::vec3, VertexElement::color, 0 },
		{ VertexElement::vec2, VertexElement::texcoord, 0 }
	};

	m_groups[GROUP_TERRAIN].vertexFormat = new VertexFormat(terrainElements, 4);
	m_groups[GROUP_TERRAIN].pipelinesBits = 0x1 << PIPELINE_TERRAIN_SOLID | 0x1 << PIPELINE_TERRAIN_WIREFRAME;

	static const VertexElement entitiesElements[] = {
		{ VertexElement::vec3, VertexElement::position, 0 },
		{ VertexElement::vec3, VertexElement::normal, 0 },
		{ VertexElement::vec2, VertexElement::texcoord, 0 }
	};

	m_groups[GROUP_ENTITIES].vertexFormat = new VertexFormat(entitiesElements, 3);
	m_groups[GROUP_ENTITIES].pipelinesBits = 0x1 << PIPELINE_ENTITIES_SOLID | 0x1 << PIPELINE_ENTITIES_WIREFRAME;
}

void ServiceGraphics::initializePipelines()
{
	dw::graphics::Renderer::State defaultStates;

	defaultStates.value(Renderer::RS_TEXTURE_2D) = true;
	defaultStates.value(Renderer::RS_CULLING) = true;
	defaultStates.value(Renderer::RS_CULL_MODE) = dw::graphics::Renderer::CullMode::BACK;
	defaultStates.value(Renderer::RS_SHADE_MODE) = dw::graphics::Renderer::ShadeMode::GOURAUD;
	defaultStates.value(Renderer::RS_FILL_MODE) = dw::graphics::Renderer::FillMode::SOLID;
	defaultStates.value(Renderer::RS_LIGHTING) = true;
	defaultStates.value(Renderer::RS_ALPHA_TEST) = true;
	defaultStates.value(Renderer::RS_ALPHA_FUNC) = dw::graphics::Renderer::GREATER;
	defaultStates.value(Renderer::RS_DEPTH_TEST) = true;
	defaultStates.value(Renderer::RS_DEPTH_FUNC) = dw::graphics::Renderer::LESS;

	{
		{
			graphics::Pipeline* pipeline = new graphics::Pipeline("TerrainSolid");

			graphics::Pipeline::Setup& setup = pipeline->getSetup();
			dw::graphics::Renderer::State& states = setup.states;

			states = defaultStates;

			setup.light = new dw::graphics::Light(dw::graphics::Light::DIRECTIONAL);
			setup.light->setAmbient(dw::graphics::Colorf(0.2f, 0.2f, 0.2f, 1.0f));
			setup.light->setDiffuse(dw::graphics::Colorf(1.0f, 1.0f, 1.0f, 1.0f));
			setup.light->setDirection(dw::maths::vec4(-1.0f, -1.0f, -1.0f, 1.0f));

			setup.texture = dw::graphics::TextureManager::getInstance().getTexture("terrain.png");

			m_pipelines[PIPELINE_TERRAIN_SOLID] = pipeline;
		}
		{
			graphics::Pipeline* pipeline = new graphics::Pipeline("TerrainWireframe");

			graphics::Pipeline::Setup& setup = pipeline->getSetup();
			dw::graphics::Renderer::State& states = setup.states;

			states = defaultStates;
			states.value(Renderer::RS_TEXTURE_2D) = false;
			states.value(Renderer::RS_FILL_MODE) = dw::graphics::Renderer::FillMode::WIREFRAME;
			states.value(Renderer::RS_LIGHTING) = false;
			states.value(Renderer::RS_ALPHA_TEST) = false;
			states.value(Renderer::RS_DEPTH_FUNC) = dw::graphics::Renderer::LEQUAL;

			m_pipelines[PIPELINE_TERRAIN_WIREFRAME] = pipeline;
		}
	}
	{
		{
			graphics::Pipeline* pipeline = new graphics::Pipeline("EntitiesSolid");

			graphics::Pipeline::Setup& setup = pipeline->getSetup();
			dw::graphics::Renderer::State& states = setup.states;

			states = defaultStates;

			setup.light = new dw::graphics::Light(dw::graphics::Light::DIRECTIONAL);
			setup.light->setAmbient(dw::graphics::Colorf(0.2f, 0.2f, 0.2f, 1.0f));
			setup.light->setDiffuse(dw::graphics::Colorf(1.0f, 1.0f, 1.0f, 1.0f));
			setup.light->setDirection(dw::maths::vec4(-1.0f, -1.0f, -1.0f, 1.0f));

			setup.texture = dw::graphics::TextureManager::getInstance().getTexture("terrain.png");

			m_pipelines[PIPELINE_ENTITIES_SOLID] = pipeline;
		}
		{
			graphics::Pipeline* pipeline = new graphics::Pipeline("EntitiesWireframe");

			graphics::Pipeline::Setup& setup = pipeline->getSetup();
			dw::graphics::Renderer::State& states = setup.states;

			states = defaultStates;
			states.value(Renderer::RS_TEXTURE_2D) = false;
			states.value(Renderer::RS_FILL_MODE) = dw::graphics::Renderer::FillMode::WIREFRAME;
			states.value(Renderer::RS_LIGHTING) = false;
			states.value(Renderer::RS_ALPHA_TEST) = false;
			states.value(Renderer::RS_DEPTH_FUNC) = dw::graphics::Renderer::LEQUAL;

			m_pipelines[PIPELINE_ENTITIES_WIREFRAME] = pipeline;
		}
	}

	m_pipelines[PIPELINE_TERRAIN_WIREFRAME]->setVisible(false);
	m_pipelines[PIPELINE_ENTITIES_WIREFRAME]->setVisible(false);
}

struct EntityVertex
{
	dw::maths::vec3 pos;
	dw::maths::vec3 normal;
	dw::maths::vec2 texcoord;
};

void ServiceGraphics::initializeMeshes()
{
	initializeCollectiblesBlocksMeshes();
	initializeCollectiblesItemsMeshes();
}

void ServiceGraphics::initializeCollectiblesBlocksMeshes()
{
	const MaterialLibrary& materialLib = WorldGridManager::getInstance().getPlanet()->getMaterialLibrary();

	static const dw::uint32 verticesCounts = 6 * 4;

	static const dw::uint32 indicesCount = 6 * 3 * 2;
	static const dw::uint16 indices[indicesCount] = {
		0,  1,  2,  2,  3,  0, // back
		4,  5,  6,  6,  7,  4, // left
		8,  9, 10, 10, 11,  8, // front
		12, 13, 14, 14, 15, 12, // right
		16, 17, 18, 18, 19, 16, // top
		20, 21, 22, 22, 23, 20  // bottom
	};

	for (dw::uint32 materialIt = 0; materialIt < materialLib.getMaterialCount(); ++materialIt)
	{
		const Material& material = materialLib.getMaterialFromId(materialIt);
		graphics::Mesh* newMesh = createMesh< graphics::SimpleMesh >(GROUP_ENTITIES, CollectiblesBlocksPath + material.getName());

		dw::float32 cellSize = 1.0f / 16.0f;
		dw::maths::vec2 back = material.getOffsetSide();
		dw::maths::vec2 side = material.getOffsetSide2();
		dw::maths::vec2 top = material.getOffsetTop();
		dw::maths::vec2 bottom = material.getOffsetBottom();

		using dw::maths::vec2;
		using dw::maths::vec3;

		EntityVertex vertices[verticesCounts] = {
			// back
			{ vec3(-0.2f, -0.2f, -0.2f), vec3( 0.0f,  0.0f, -1.0f), vec2(back.x, back.y + cellSize) },
			{ vec3(-0.2f,  0.2f, -0.2f), vec3( 0.0f,  0.0f, -1.0f), vec2(back.x, back.y) },
			{ vec3( 0.2f,  0.2f, -0.2f), vec3( 0.0f,  0.0f, -1.0f), vec2(back.x + cellSize, back.y) },
			{ vec3( 0.2f, -0.2f, -0.2f), vec3( 0.0f,  0.0f, -1.0f), vec2(back.x + cellSize, back.y + cellSize) },
			// left
			{ vec3( 0.2f, -0.2f, -0.2f), vec3( 1.0f,  0.0f,  0.0f), vec2(side.x, side.y + cellSize) },
			{ vec3( 0.2f,  0.2f, -0.2f), vec3( 1.0f,  0.0f,  0.0f), vec2(side.x, side.y) },
			{ vec3( 0.2f,  0.2f,  0.2f), vec3( 1.0f,  0.0f,  0.0f), vec2(side.x + cellSize, side.y) },
			{ vec3( 0.2f, -0.2f,  0.2f), vec3( 1.0f,  0.0f,  0.0f), vec2(side.x + cellSize, side.y + cellSize) },
			// front
			{ vec3( 0.2f, -0.2f,  0.2f), vec3( 0.0f,  0.0f,  1.0f), vec2(back.x, back.y + cellSize) },
			{ vec3( 0.2f,  0.2f,  0.2f), vec3( 0.0f,  0.0f,  1.0f), vec2(back.x, back.y) },
			{ vec3(-0.2f,  0.2f,  0.2f), vec3( 0.0f,  0.0f,  1.0f), vec2(back.x + cellSize, back.y) },
			{ vec3(-0.2f, -0.2f,  0.2f), vec3( 0.0f,  0.0f,  1.0f), vec2(back.x + cellSize, back.y + cellSize) },
			// right											   
			{ vec3(-0.2f, -0.2f,  0.2f), vec3(-1.0f,  0.0f,  0.0f), vec2(side.x, side.y + cellSize) },
			{ vec3(-0.2f,  0.2f,  0.2f), vec3(-1.0f,  0.0f,  0.0f), vec2(side.x, side.y) },
			{ vec3(-0.2f,  0.2f, -0.2f), vec3(-1.0f,  0.0f,  0.0f), vec2(side.x + cellSize, side.y) },
			{ vec3(-0.2f, -0.2f, -0.2f), vec3(-1.0f,  0.0f,  0.0f), vec2(side.x + cellSize, side.y + cellSize) },
			// top
			{ vec3(-0.2f,  0.2f, -0.2f), vec3( 0.0f,  1.0f,  0.0f), vec2(top.x, top.y + cellSize) },
			{ vec3(-0.2f,  0.2f,  0.2f), vec3( 0.0f,  1.0f,  0.0f), vec2(top.x, top.y) },
			{ vec3( 0.2f,  0.2f,  0.2f), vec3( 0.0f,  1.0f,  0.0f), vec2(top.x + cellSize, top.y) },
			{ vec3( 0.2f,  0.2f, -0.2f), vec3( 0.0f,  1.0f,  0.0f), vec2(top.x + cellSize, top.y + cellSize) },
			// bottom
			{ vec3(-0.2f, -0.2f,  0.2f), vec3( 0.0f, -1.0f,  0.0f), vec2(bottom.x, bottom.y + cellSize) },
			{ vec3(-0.2f, -0.2f, -0.2f), vec3( 0.0f, -1.0f,  0.0f), vec2(bottom.x, bottom.y) },
			{ vec3( 0.2f, -0.2f, -0.2f), vec3( 0.0f, -1.0f,  0.0f), vec2(bottom.x + cellSize, bottom.y) },
			{ vec3( 0.2f, -0.2f,  0.2f), vec3( 0.0f, -1.0f,  0.0f), vec2(bottom.x + cellSize, bottom.y + cellSize) }
		};

		dw::graphics::IndexBuffer& indexBuffer = newMesh->getIndexBuffer();
		dw::graphics::VertexBuffer& vertexBuffer = newMesh->getVertexBuffer();

		indexBuffer.bind();
		indexBuffer.resize(indicesCount);
		indexBuffer.write(indices);
		indexBuffer.unbind();

		vertexBuffer.bind();
		vertexBuffer.resize(verticesCounts);
		vertexBuffer.write(vertices);
		vertexBuffer.unbind();
	}
}

void ServiceGraphics::initializeCollectiblesItemsMeshes()
{
	const ItemMaterialLibrary& materialLib = WorldGridManager::getInstance().getPlanet()->getItemMaterialLibrary();

	static const dw::uint32 verticesCounts = 2 * 4;

	static const dw::uint32 indicesCount = 2 * 3 * 2;
	static const dw::uint16 indices[indicesCount] = {
		0,  1,  2,  2,  3,  0,
		4,  5,  6,  6,  7,  4
	};

	for (dw::uint32 materialIt = 0; materialIt < materialLib.getMaterialCount(); ++materialIt)
	{
		const ItemMaterial& material = materialLib.getMaterialFromId(materialIt);
		graphics::Mesh* newMesh = createMesh< graphics::SimpleMesh >(GROUP_ENTITIES, CollectiblesItemsPath + material.getName());

		dw::float32 cellSize = 1.0f / 16.0f;
		dw::maths::vec2 offset = material.getOffset();

		using dw::maths::vec2;
		using dw::maths::vec3;

		dw::float32 n = sqrtf(0.5f);

		EntityVertex vertices[verticesCounts] = {
			{ vec3(-0.2f, -0.2f, -0.2f), vec3(-n, 0.0f,  n), vec2(offset.x, offset.y + cellSize) },
			{ vec3(-0.2f,  0.2f, -0.2f), vec3(-n, 0.0f,  n), vec2(offset.x, offset.y) },
			{ vec3( 0.2f,  0.2f,  0.2f), vec3(-n, 0.0f,  n), vec2(offset.x + cellSize, offset.y) },
			{ vec3( 0.2f, -0.2f,  0.2f), vec3(-n, 0.0f,  n), vec2(offset.x + cellSize, offset.y + cellSize) },
			{ vec3(-0.2f, -0.2f,  0.2f), vec3( n, 0.0f, -n), vec2(offset.x, offset.y + cellSize) },
			{ vec3(-0.2f,  0.2f,  0.2f), vec3( n, 0.0f, -n), vec2(offset.x, offset.y) },
			{ vec3( 0.2f,  0.2f, -0.2f), vec3( n, 0.0f, -n), vec2(offset.x + cellSize, offset.y) },
			{ vec3( 0.2f, -0.2f, -0.2f), vec3( n, 0.0f, -n), vec2(offset.x + cellSize, offset.y + cellSize) }
		};

		dw::graphics::IndexBuffer& indexBuffer = newMesh->getIndexBuffer();
		dw::graphics::VertexBuffer& vertexBuffer = newMesh->getVertexBuffer();

		indexBuffer.bind();
		indexBuffer.resize(indicesCount);
		indexBuffer.write(indices);
		indexBuffer.unbind();

		vertexBuffer.bind();
		vertexBuffer.resize(verticesCounts);
		vertexBuffer.write(vertices);
		vertexBuffer.unbind();
	}
}

} // namespace infinity
